package interfaces;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import exceptions.ExAddressInput;
import exceptions.ExPortConnection;
import protocols.EthernetII;

public class Port {
	
	private EthernetII.MACaddress MACaddress;
	private boolean status_linked = false;
	private Port connected_port = null;
	private Port.Listen rcv_thread;
	private Port.Send snd_thread;
	private RecieveBuffer rcv_buffer = new RecieveBuffer();
	private SendBuffer snd_buffer = new SendBuffer();
	
		
	public EthernetII getRcv_buffer() {
		return rcv_buffer.content;
	}
	
	public EthernetII getSnd_buffer() {
		return snd_buffer.content;
	}
	
	public Port getConnected_port() {
		return connected_port;
	}
	
	public EthernetII.MACaddress getMACaddress() {
		return MACaddress;
	}
	
	public boolean isStatus_linked() {
		return status_linked;
	}
	
	private class SendBuffer {
		private EthernetII content = null;
		private ReentrantLock lock = new ReentrantLock(false);
		private Condition condVar = lock.newCondition();		
	}
	
	private class RecieveBuffer{
		private EthernetII content = null;
		private ReentrantLock lock = new ReentrantLock(false);
		private Condition condVar = lock.newCondition();
	}
		
	public Port(String s) throws ExAddressInput {
		MACaddress = new EthernetII.MACaddress(s);
	}
	
	public void connect(Port port) throws ExPortConnection {
		if (status_linked || port.status_linked) throw new ExPortConnection("Jedan ili oba porta vec povezani");
		else connected_port = port;
		port.connected_port = this;
		status_linked = true;
		port.status_linked = true;
		port.rcv_thread = port.new Listen();
		this.rcv_thread = this.new Listen();		
	}

	public void disconnect() throws ExPortConnection {
//		System.out.println("Disconnecting- started");
		this.snd_buffer.lock.lock();
//		System.out.println("Disconnecting- locked");
		if (!status_linked) throw new ExPortConnection("Port nije povezan");
		this.connected_port.rcv_thread.stopthread();
		this.rcv_thread.stopthread();
		this.connected_port.status_linked = false;
		this.connected_port.connected_port = null;
		connected_port = null;
		status_linked = false;
//		System.out.println("Disconnecting- over");
		this.snd_buffer.lock.unlock();
//		System.out.println("Disconnecting- unlocked");
	}
	
	public void send(EthernetII frame) throws InterruptedException {
		if (this.status_linked) {				
			if (this.snd_buffer.content != null && !snd_buffer.lock.isLocked()) {
				snd_buffer.lock.lock();
//				System.out.println("Sending on tis port in process. Waiting...");				
				while (this.snd_buffer.content != null) {
					snd_buffer.condVar.awaitUninterruptibly();
				}
				snd_buffer.lock.unlock();
//				System.out.println("Sending continue");
			}
			snd_buffer.lock.lock();
			try {
//				System.out.println(" poiuz");
				this.snd_buffer.content = frame;
//				System.out.println(this.snd_buffer.content);
				
				this.snd_thread = this.new Send();
				} finally {
					snd_buffer.lock.unlock();
//					System.out.println(" qqqqq");				
				}				
		}		
	}
	
	private class Send extends Thread {
		public Send() {
			start();
		}
		public void run() {
			Port.this.snd_buffer.lock.lock();
			try {
				if (Port.this.isStatus_linked()) {					
//					for (int i= 0; i<100; i++) {System.out.println(this.toString() +" "+ i);}
					Port.this.connected_port.rcv_buffer.lock.lock();
					try {
						Port.this.connected_port.rcv_buffer.content = Port.this.snd_buffer.content;
						Port.this.connected_port.rcv_buffer.condVar.signal();
					} finally {
						Port.this.connected_port.rcv_buffer.lock.unlock();
					}
					Port.this.snd_buffer.content = null;
//					if (Port.this.snd_buffer.lock.hasWaiters(Port.this.snd_buffer.condVar)) {
						Port.this.snd_buffer.condVar.signal();
//					}
//					System.out.println(this.toString() + " zavrsio");
				}else {
					System.err.println("- Sending failed -");
				}
			}finally {
			Port.this.snd_buffer.lock.unlock();
			}
		}
	}
	
	private class Listen extends Thread {
		
		private volatile boolean stopthread = false;
		
		public Listen() {
			setDaemon(true);
			start();			
		}
		public void stopthread() {
				stopthread = true;
				Port.this.rcv_thread.interrupt();
		}
		public void run() {
			while(!stopthread) {
				Port.this.rcv_buffer.lock.lock();
				try {
					try {
						while (Port.this.rcv_buffer.content == null) {
						Port.this.rcv_buffer.condVar.await();
						}
					} catch (InterruptedException e) {
						System.err.println("Recievig process interrupted");
						return;
					}
//					for (int i= 1000; i<1200; i++) {System.out.println(Port.this.toString() +"         "+ i);}
					System.out.println(Port.this.rcv_buffer.content);
				
					System.out.println(this.toString() + " Receiving process over");
					Port.this.rcv_buffer.content = null;				
				}finally {
					Port.this.rcv_buffer.lock.unlock();
				}
			}
		}	
	}
}

